package com.ibm.cps.message;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.ibm.cps.processors.ProcessorFactory;
import com.ibm.cps.processors.ProcessorsGroup;
import com.ibm.util.ErrorCode;
import com.ibm.util.JSONUtility;
import com.ibm.util.TopologySort;
import com.ibm.util.exception.CPSException;

public class MetadataTopologySort {

	static public List<ObjectNode> getSortedMetadatas(Collection<String> input)
			throws CPSException {
		if (input == null) {
			return null;
		}
		ArrayNode array = JSONUtility.newArrayNode();
		for (String metadata : input) {
			array.add(JSONUtility.fromObjectString(metadata));
		}
		return getSortedMetadatas(array);
	}

	// static public ArrayList<ObjectNode> getSortedMetadatas(ArrayNode objects)
	// throws CPSException {
	// if (objects == null) {
	// return null;
	// }
	// ArrayList<String> types = ValidatorFactory.getInputTypes();
	// HashMap<String, HashSet<ObjectNode>> type2Processors =
	// getType2Processors(types);
	//
	// for (int i = 0; i < objects.size(); i++) {
	// ObjectNode ele = (ObjectNode) objects.get(i);
	// String type = JSONUtility.GetString(MessageFields.TYPE, ele);
	// if (type2Processors.get(type) == null) {
	// throw new CPSException(ErrorCode.ERROR_PROCESSOR_TYPE_CODE,
	// "Error input type " + type + ".");
	// }
	// type2Processors.get(type).add(ele);
	// }
	//
	// ArrayList<ObjectNode> sortedMetadatas = new ArrayList<ObjectNode>();
	// for (String type : types) {
	// for (ObjectNode ele : type2Processors.get(type)) {
	// sortedMetadatas.add(ele);
	// }
	// }
	// return sortedMetadatas;
	// }
	//
	// static private HashMap<String, HashSet<ObjectNode>> getType2Processors(
	// ArrayList<String> types) {
	// HashMap<String, HashSet<ObjectNode>> type2Processors = new
	// HashMap<String, HashSet<ObjectNode>>();
	//
	// for (String type : types) {
	// type2Processors.put(type, new HashSet<ObjectNode>());
	// }
	// return type2Processors;
	// }

	static public List<ObjectNode> getSortedMetadatas(ArrayNode objects)
			throws CPSException {
		if (objects == null) {
			return null;
		}

		Multimap<String, String> parent2Successors = ArrayListMultimap.create();
		Map<String, ObjectNode> id2ObjectMap = new HashMap<String, ObjectNode>();
		List<ObjectNode> groupMetadatas = new ArrayList<ObjectNode>();
		String processorid;
		String type;
		/* construct parent to successors map */
		for (int i = 0; i < objects.size(); i++) {
			System.out.println(objects.get(i));
			ObjectNode object = (ObjectNode) objects.get(i);
			type = JSONUtility.GetString(MessageFields.TYPE, object);
			if (type == null) {
				throw new CPSException(ErrorCode.INVALID_INPUT_ERROR,
						"Please provoide the value of type in " + object);
			}
			if (ProcessorsGroup.isGroupType(type)) {
				groupMetadatas.add(object);
			} else if (ProcessorFactory.getInstance().isProcessorType(type)) {
				processorid = JSONUtility.GetString(MessageFields.PROCESSORID,
						object);
				if (processorid == null) {
					throw new CPSException(ErrorCode.INVALID_INPUT_ERROR,
							"Please provoide the value of processorid in "
									+ object);
				}
				id2ObjectMap.put(processorid, object);
				if (JSONUtility.isArrayField(object,
						MessageFields.PARENTPROCESSORS)) {
					ArrayNode parents = (ArrayNode) object
							.get(MessageFields.PARENTPROCESSORS);
					for (int j = 0; j < parents.size(); j++) {
						parent2Successors.put(parents.get(j).asText(),
								processorid);
					}
				} else if (JSONUtility.isStringField(object,
						MessageFields.PARENTPROCESSORS)) {
					parent2Successors.put(JSONUtility.GetString(
							MessageFields.PARENTPROCESSORS, object),
							processorid);
				}
			} else {
				throw new CPSException(ErrorCode.INVALID_INPUT_ERROR,
						"Invalide type " + type + " in " + object);
			}
		}
		List<String> sortedList = TopologySort.sortTopology(parent2Successors
				.asMap());

		ArrayList<ObjectNode> sortedMetadatas = new ArrayList<ObjectNode>();
		sortedMetadatas.addAll(groupMetadatas);
		for (String id : sortedList) {
			if (id2ObjectMap.get(id) != null) {
				sortedMetadatas.add(id2ObjectMap.get(id));
			}
		}
		return sortedMetadatas;
	}
}
